/*
 * -----------------------------------------------------------------------------
 * Project  : ODBC Support
 * Purpose  : Support ODBC connectivity.
 * File     : Win32.Data.Odbc.cpp
 * Author   : Eduardo Sobrino
 * Date     : Nov/98
 */

#pragma region   // Include Files

#include "stdafx.h"

#include "Win32.Data.Odbc.h"
#include <sql.h>
#include <stdio.h>

#pragma endregion

// -----------------------------------------------------------------------------

namespace Kif
{
namespace Win32
{
namespace Data
{
namespace OdbcClient
{

// -----------------------------------------------------------------------------
#pragma region // Define OdbcError

/// <summary>
/// This method iterates though the error stack for the handle passed into the
/// constructor and displays those errors on the console. Caller is responsable
/// for the dispose of returned Kif::Win32::String.
/// </summary>
Kif::Win32::String *OdbcErrors::GetErrorString()
{
   Kif::Win32::String *strText = new Kif::Win32::String();

   SQLSMALLINT i = 0, len = 0;
   SQLINTEGER  native;
   SQLTCHAR    state[9], text[256];
   SQLRETURN   sqlReturn = SQL_SUCCESS;

   if ( m_handle == SQL_NULL_HANDLE )
   {
      strText->Copy(L"The error handle is not a valid handle.\n");
      strText->Concat(m_iLine);
      return strText;
   }

   wprintf_s(TEXT("Error Line(%d)\n"), m_iLine);

   while( sqlReturn == SQL_SUCCESS )
   {
      len = 0;

      sqlReturn = SQLGetDiagRec(
         m_type,
         m_handle,
         ++i,
         state,
         &native,
         text,
         sizeof(text)/sizeof(SQLTCHAR),
         &len);

      if ( SQL_SUCCEEDED(sqlReturn) )
      {
         strText->Copy(L"Error(");
         strText->Concat(i);
         strText->Concat(L" ,");
         strText->Concat(native);
         strText->Concat(L" ,");
         strText->Concat(state);
         strText->Concat(L") : ");
         strText->Concat(text);
         strText->Concat(L"\n");
      }
   }

   return strText;
}  // end of GetErrorText

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcParameter

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcDatabase

/// <summary>Initialize the database environment</summary>
OdbcDatabase::OdbcDatabase()
{
   Verbose = Kif::Win32::Diagnostics::Verbose_None;
   hEnvironment = NULL ;
   hDatabaseConnection = NULL ;

   LastReturnCode = SQLAllocEnv(&hEnvironment) ;
   if (LastReturnCode == SQL_SUCCESS)
   {
      LastReturnCode = SQLAllocConnect(hEnvironment,&hDatabaseConnection) ;
      if (LastReturnCode == SQL_SUCCESS)
         Available = true ;
   }
   else
      Available = false ;
   Connected = false ;

   // set support defaults...

   DateType     = SQL_DATE ;

   // setup default timeout for connection and commands ...

   ConnectionTimeout = -1 ;
   CommandTimeout    = -1 ;

   // query database capabilities...

   if (Available)
   {
      SQLUINTEGER fFlags ;

      SQLGetInfo(hDatabaseConnection,
         SQL_CONVERT_TIMESTAMP,(SQLPOINTER)&fFlags,sizeof(fFlags),NULL) ;
      if (fFlags & SQL_CVT_DATE)
         DateType = SQL_TIMESTAMP ;
      else
         DateType = SQL_DATE ;
   }
}  // end of OdbcDatabase (ctor)

/// <summary>Get error code. The sql state is mapped to an internal error code.
/// </summary>
/// <param name="sqlState">sql state id string</param>
/// <returns>return the error code</returns>
/// <date>Aug/2k (ESob)</date>

long OdbcDatabase::GetErrorCode(SqlChar *sqlState)
{

   // CAPABILITIES ERRORS: see if capability is not supported...
   if (!Kif::Win32::String::Compare((TCHAR*)sqlState,TEXT("IM001")))
      DatabaseErrorCode = OdbcError_CAPABILITY_NOT_SUPPORTED ;
   else
   if (!Kif::Win32::String::Compare((TCHAR*)sqlState,TEXT("S1C00")))
      DatabaseErrorCode = OdbcError_OFEATURE_NOT_IMPLEMENTED ;

   // FATAL ERRORS: try to react to these fatal errors ...

   else
   // communication link failure
   if (!Kif::Win32::String::Compare((TCHAR*)sqlState,TEXT("08001")))
      DatabaseErrorCode = OdbcError_FATALERROR_FAILTOCONNECT ;

   else
   if (!Kif::Win32::String::Compare((TCHAR*)sqlState,TEXT("08S01")))
      DatabaseErrorCode = OdbcError_FATALERROR_COMMFAILURE ;

   // NO ERRORS: else all is ok ...

   else
      DatabaseErrorCode = OdbcError_SUCCESS ;

   return(DatabaseErrorCode) ;
}  // end of get error code from sql state ...

/// <sumarry>Get error based on handle type</summary>
/// <param name="handleType">handle type (SQLSMAILINT)</param>
/// <param name="handle">handle</param>
/// <date>Nov/98  (ESob)</date>
bool OdbcDatabase::CheckError(SQLSMALLINT handleType,SQLHANDLE handle)
{
   SQLSMALLINT recnumber = 1 ;
   SqlChar     sqlstate[MinStrLen] ;
   SQLINTEGER  nativeerr ;
   SqlChar     message[MaxStrLen+1] ;
   SQLSMALLINT messlen ;
   bool        ok ;

   *message = (SCHAR)NULL ;
   SQLRETURN lerror = SQLGetDiagRec(
      handleType,handle,recnumber,sqlstate,&nativeerr,
      message,(SQLSMALLINT)MaxStrLen,&messlen) ;

   if ((lerror == SQL_SUCCESS) ||
       (lerror == SQL_SUCCESS_WITH_INFO))
      ok = true ;
   else
   if (lerror == SQL_INVALID_HANDLE)
      ok = false ;
   else
   if (lerror == SQL_ERROR)
      ok = false ;
   else
   if (lerror == SQL_NO_DATA)
      ok = false ;
   else
      ok = false ;

   // get error code ...

   DatabaseErrorCode = GetErrorCode(sqlstate) ;
   return(ok);
}  // end of get error based on handle type

/// <sumarry>Get error based on handle type</summary>
/// <param name="handleType">handle type (SQLSMAILINT)</param>
/// <param name="handle">handle</param>
/// <date>Nov/98  (ESob)</date>
void OdbcDatabase::GetError(SQLSMALLINT handleType,SQLHANDLE handle)
{
   SQLSMALLINT recnumber = 1;
   SqlChar     sqlstate[MinStrLen];
   SQLINTEGER  nativeerr;
   SqlChar     message[MaxStrLen+1];
   SQLSMALLINT messlen;
   bool done;

   hLastStatement = handle ;

   DatabaseErrorCode = OdbcError_SUCCESS;
   *message = (SqlChar)NULL;

   SQLRETURN lerror = SQLGetDiagRec(
      handleType,handle,recnumber,sqlstate,&nativeerr,
      message,(SQLSMALLINT)MaxStrLen,&messlen) ;

   if ((lerror == SQL_SUCCESS) ||
       (lerror == SQL_SUCCESS_WITH_INFO))
      done = true ;
   else
   if (lerror == SQL_INVALID_HANDLE)
      done = false ;
   else
   if (lerror == SQL_ERROR)
      done = false ;
   else
   if (lerror == SQL_NO_DATA)
      done = false ;

   if (Verbose == Kif::Win32::Diagnostics::Verbose_Trace)
   {
      if (*message)
      {
         Kif::Win32::String mess;

         if (ErrorHeaderString.GetLength() > 0)
            mess.Copy(ErrorHeaderString);

         mess.Concat(message);
         mess.Concat(_T(" "),1);
         mess.Concat(sqlstate,Kif::Win32::String::GetLength(sqlstate));

         Kif::Win32::Diagnostics::Log::WriteMessage(mess.GetBuffer()) ;
      }
   }

   // get error code ...
   DatabaseErrorCode = GetErrorCode(sqlstate) ;

}  // end of get error based on handle type

/// <sumarry>Get error string. The length of the message string is returned
/// </summary>
/// <param name="errorMessage">error mesage string</param>
/// <date>Jul/2000  (ESob)</date>
int OdbcDatabase::GetError(Kif::Win32::String &errorMessage)
{
   SQLSMALLINT recnumber = 1 ;
   SqlChar     sqlstate[MinStrLen] ;
   SQLINTEGER  nativeerr ;
   SqlChar     message[MaxStrLen+1] ;
   SQLSMALLINT messlen ;
   SQLSMALLINT htype  = SQL_HANDLE_STMT ;
   SQLHANDLE   handle = hLastStatement ;

   DatabaseErrorCode = OdbcError_SUCCESS ;
   *message = (SCHAR)NULL ;
   SQLRETURN lerror = SQLGetDiagRec(htype,handle,recnumber,sqlstate,&nativeerr,
      message,(SQLSMALLINT)MaxStrLen,&messlen) ;

   if (messlen > 0)
   {
      if (ErrorHeaderString.GetLength() > 0)
         errorMessage.Copy(ErrorHeaderString);
      errorMessage.Concat(message,messlen);
   }
   else
      messlen = 0 ;

   return(messlen) ;
}  // end of get error

/// <summary>Release all ODBC allocated resources...</summary>
/// <date>Nov/98  (ESob)</date>
void OdbcDatabase::Dispose()
{
   if (Available)
   {
      // free ODBC environment handle, if any...
      if (hEnvironment != SQL_NULL_HENV)
         SQLFreeEnv(hEnvironment) ;
   }

   Available = FALSE ;
}  // end of release all ODBC allocated resources...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcConnection

/// <summary>Set default connection timeout</summary>
/// <param name="lNewTimeout">new timeout time in seconds (long)</param>
/// <date>Oct/2k2  (ESob)</date>
/// <returns>true is returned if done</returns>
bool OdbcConnection::SetConnectionTimeout(long timeout)
{
   bool done = true;
   Database.ConnectionTimeout = timeout ;

   // SQLSetConnectAttr SQL_LOGIN_TIMEOUT
   if ((timeout != -1) && (Database.hDatabaseConnection != NULL))
   {
      SQLRETURN retcode = SQLSetConnectOption( // SQLSetConnectAttr(
         Database.hDatabaseConnection, SQL_LOGIN_TIMEOUT, timeout) ; //, 0) ;
      done = (retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO) ;

      // if there is a mesage display it ...
      if (retcode != SQL_SUCCESS)
      {
         Database.GetError(SQL_HANDLE_DBC,Database.hDatabaseConnection) ;
      }
   }

   return(done) ;
}  // end of set connection timeout

/// <summary>Set default command timeout</summary>
/// <param name="lNewTimeout">new timeout time in seconds (long)</param>
/// <date>Oct/2k2  (ESob)</date>
/// <returns>true is returned if done</returns>
bool OdbcConnection::SetCommandTimeout(long timeout)
{  bool done = true;
   Database.CommandTimeout = timeout ;
   return(done) ;
}  // end of set command timeout

/// <summary>Set default command timeout</summary>
/// <param name="hStatement">statement handle</param>
/// <date>Oct/2k2  (ESob)</date>
/// <returns>true is returned if done</returns>
bool OdbcConnection::SetCommandTimeout(SQLHSTMT hStatement)
{  bool done = false;
   // SQLSetStmtAttr SQL_QUERY_TIMEOUT 
   if ((Database.CommandTimeout != -1) && (hStatement != NULL))
   {
      SQLRETURN retcode = SQLSetStmtOption( // SQLSetStmtAttr(
         hStatement, SQL_QUERY_TIMEOUT, Database.CommandTimeout) ; //, 0) ;
      done = (retcode == SQL_SUCCESS) || (retcode == SQL_SUCCESS_WITH_INFO);
   }
   return(done) ;
}  // end of set command timeout

/// <sumarry>Connect to database using given data source info</summary>
/// <param name="dataSource">data source info to connect to</param>
/// <returns>true is returned if connected</returns>
/// <date>Nov/98  (ESob)</date>
bool OdbcConnection::Connect(DataSourceInfo &dataSource)
{
   DataSource.Copy(dataSource);

   if (!Database.Available)
   {
      if (Database.hDatabaseConnection == SQL_NULL_HDBC)
      {
         Database.LastReturnCode =
            SQLAllocConnect(Database.hEnvironment,
            &Database.hDatabaseConnection);
         Database.Available = (Database.LastReturnCode == SQL_SUCCESS) ;
      }
      else
         Database.Available = TRUE ;
   }

   if (Database.Available)
   {

      Database.LastReturnCode = SQLConnect(
         Database.hDatabaseConnection,
            (SqlChar*)dataSource.SourceName.GetBuffer(), SQL_NTS,
            (SqlChar*)dataSource.UserId.GetBuffer(),     SQL_NTS,
            (SqlChar*)dataSource.Password.GetBuffer(),   SQL_NTS) ;

      Database.Connected = Database.Success() ;

      if (!Database.Connected)
         Database.GetError(SQL_HANDLE_DBC,Database.hDatabaseConnection) ;
   }
   else
   {
      Kif::Win32::String ostr;

      TCHAR couldnt[] = _T("Could not connect: <not available> Alias(");
      TCHAR uid[]     = _T(") uid(");
      TCHAR end[]     = _T(")");

      if (Database.ErrorHeaderString.GetLength() > 0)
         ostr.Copy(Database.ErrorHeaderString);

      ostr.Concat(couldnt,SizeOf(couldnt));
      ostr.Concat(dataSource.SourceName);
      ostr.Concat(uid,SizeOf(uid));
      ostr.Concat(dataSource.UserId);
      ostr.Concat(end,SizeOf(end));

      Kif::Win32::Diagnostics::Log::WriteMessage(ostr.GetBuffer());
   }

   return(Database.Connected) ;
}  // end of Connect

/// <sumarry>Disconnect from the database</summary>
/// <date>Nov/98  (ESob)</date>
void OdbcConnection::Disconnect()
{
   if (Database.Available && Database.Connected)
   {

      // disconnect from the database, and
      // free ODBC connection handle, if any...

      if (Database.hDatabaseConnection != SQL_NULL_HDBC)
      {
         SQLDisconnect(Database.hDatabaseConnection) ;
         SQLFreeConnect(Database.hDatabaseConnection) ;
         Database.hDatabaseConnection = SQL_NULL_HDBC ;
         Database.Available = false ;
      }
   }

   Database.Connected = false ;
}  // end of disconnect...

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcCommand

/// <summary>Bind fields</summary>
/// <param name="hStatement">handle to statement</param>
/// <param name="columns">columsn to bind</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>returns true if fields where binded</returns>
bool OdbcCommand::BindColumns(
   SQLHSTMT hStatement, OdbcColumnsCollection &columns)
{
   SQLRETURN rval = 0;

   bool  done;
   DWORD count;
   int   bsize, indx = 0;
   SQLINTEGER cd;
   SQLINTEGER cdImageSize;
   OdbcColumn *col;

   for (count = 0; count < columns.Count; count++)
   {
      indx++ ;
      col   = columns.getItem(count);
      bsize = col->GetSizeInBytes();

      switch (col->GetType())
      {
         case Kif::Win32::BaseType_AnsiString:
            cd = bsize;
            rval = SQLBindCol(hStatement,indx,SQL_C_CHAR,
               (SQLPOINTER)col->getValue(),bsize,&cd);
            break ;

         case Kif::Win32::BaseType_WideString:
            cd = bsize;
            rval = SQLBindCol(hStatement,indx,SQL_C_WCHAR,
               (SQLPOINTER)col->getValue(),bsize,&cd);
            break ;

         case Kif::Win32::BaseType_Int32:
            rval = SQLBindCol(hStatement,indx,SQL_C_LONG,
               (SQLPOINTER)col->getValue(),0,&cd);
            break ;

         case Kif::Win32::BaseType_Double:
            rval = SQLBindCol(hStatement,indx,SQL_C_DOUBLE,
               (SQLPOINTER)col->getValue(),
               0,&cd);
            break ;

         case Kif::Win32::BaseType_DateTime:
            rval = SQLBindCol(hStatement,indx,SQL_C_TYPE_TIMESTAMP,
               (SQLPOINTER)col->getValue(),
               sizeof(TIMESTAMP_STRUCT),&cd);
            break;

         case Kif::Win32::BaseType_Blob:
            cdImageSize = bsize;
            col->cbBinaryData = SQL_DATA_AT_EXEC;
            rval = SQLBindCol(hStatement, indx,
               SQL_C_BINARY, (SQLPOINTER)col->GetBuffer(), 
               bsize, NULL) ;
            break;

         default :;
      }

      if ((rval != SQL_SUCCESS) || (rval == SQL_SUCCESS_WITH_INFO))
         Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
   }

   done = true ;
   return(done) ;
}  // end of BindColumns

/// <summary>Bind statement parameters</summary>
/// <param name="hStatement">handle to statement</param>
/// <param name="parameters">parameters to bind</param>
/// <date>Jan/98  (ESob)</date>
/// <returns>true is returned if params where binded</returns>
bool OdbcCommand::BindParameters(
   SQLHSTMT hStatement, OdbcParametersCollection &parameters)
{
   SQLRETURN rval = 0;
   SQLSMALLINT ioType;

   DWORD count;
   int indx = 0 ;
   OdbcParameter *param;
   DWORD bsize;
   int dec = 0;

   // BIND to statement parameters
   for (count = 0; count < parameters.Count; count++)
   {
      indx++ ;
      param  = parameters.getItem(count);
      bsize  = param->GetSizeInBytes();  // size in bytes

      switch (param->Direction)
      {
         case Kif::Win32::MemoryParamDirection_Input:
            ioType = SQL_PARAM_INPUT;
            break;
         case Kif::Win32::MemoryParamDirection_Output:
            ioType = SQL_PARAM_OUTPUT;
            break;
         case Kif::Win32::MemoryParamDirection_InputOutput:
            ioType = SQL_PARAM_OUTPUT;
            break;
         default:
            ioType = SQL_PARAM_INPUT;
            break;
      }

      switch(param->GetType())
      {
         case Kif::Win32::BaseType_WideString:
            rval = SQLBindParameter(hStatement, indx,
               ioType, SQL_C_WCHAR, SQL_CHAR,
               bsize,0,(SQLWCHAR*)param->getValue(),bsize,NULL) ;
            break;

         case Kif::Win32::BaseType_AnsiString:
            rval = SQLBindParameter(hStatement, indx,
               ioType, SQL_C_CHAR, SQL_CHAR,
               bsize,0,(SQLCHAR*)param->getValue(),bsize,NULL) ;
            break;

         case Kif::Win32::BaseType_Double:
            rval = SQLBindParameter(hStatement, indx,
               ioType,SQL_C_DOUBLE,
               SQL_DOUBLE,0,0,
               (SQLDOUBLE*)param->getValue(),0,NULL) ;
            break;

         case Kif::Win32::BaseType_Int16:
            rval = SQLBindParameter(hStatement, indx,
               ioType, SQL_C_SHORT, SQL_SMALLINT,0,0,
               (SQLSMALLINT*)param->getValue(),0,NULL) ;
            break;

         case Kif::Win32::BaseType_Int32:
            rval = SQLBindParameter(hStatement, indx,
               ioType, SQL_C_LONG, SQL_INTEGER,0,0,
               (SQLINTEGER*)param->getValue(),0,NULL) ;
            break;

         case Kif::Win32::BaseType_DateTime:
            rval = SQLBindParameter(hStatement, indx,
               ioType,SQL_C_TIMESTAMP,SQL_TIMESTAMP,0,0,
               param->getValue(),0,NULL) ;
            break;

         case Kif::Win32::BaseType_Blob:
            param->cbBinaryData = SQL_DATA_AT_EXEC;
            rval = SQLBindParameter(hStatement, indx,
               ioType, SQL_C_BINARY, SQL_LONGVARBINARY, 
               0,0,NULL,0,&param->cbBinaryData) ;
            //rval = SQLBindParameter(hStatement, indx,
            //   ioType, SQL_C_BINARY, SQL_LONGVARBINARY, 
            //   bsize,0,(SQLPOINTER)param->getValue(),bsize,NULL) ;
            break;
      }

      if ((rval != SQL_SUCCESS) || (rval == SQL_SUCCESS_WITH_INFO))
         Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
   }

   return(TRUE) ;
}  // end of BindParameters

/// <summary>Create a new statement handle</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcCommand::GetNewStatementHandle()
{
   bool done = false;
   if (Connection)
   {
      // if there is an existing statement/query dropit
      if (hStatement != SQL_NULL_HSTMT)
         SQLFreeStmt(hStatement,SQL_DROP) ;

      Connection->Database.LastReturnCode = SQLAllocHandle(SQL_HANDLE_STMT,
         Connection->Database.hDatabaseConnection,&hStatement) ;
      done = Connection->Database.Success() ;
   }
   return(done) ;
}  // end of GetStatementHandle

/// <summary>Send large data</summary>
bool OdbcCommand::SendLargeData()
{
   SQLRETURN rval = SQL_SUCCESS;
   DWORD count;
   int indx = 0 ;
   OdbcParameter *param;
   DWORD bsize;
   SQLPOINTER pAddr = NULL;

   for (count = 0; count < Parameters.Count; count++)
   {
      indx++ ;
      param  = Parameters.getItem(count);
      bsize  = param->GetSizeInBytes();  // size in bytes

      if (param->GetType() == Kif::Win32::BaseType_Blob)
      {
         pAddr = NULL;
         rval = SQLParamData(hStatement, &pAddr);
         if (rval != SQL_NEED_DATA)
            break;
         rval = SQLPutData(hStatement,(SQLPOINTER)param->getValue(),bsize);

         if ((rval != SQL_SUCCESS) || (rval == SQL_SUCCESS_WITH_INFO))
            Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
      }
   }
   rval = SQLParamData(hStatement, &pAddr);

   return((rval == SQL_SUCCESS) || (rval == SQL_SUCCESS_WITH_INFO));
}  // end of SendLargeData

/// <summary>Execute a non-query</summary>
/// <param name="commandText">command text</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcCommand::ExecuteNonQuery(Kif::Win32::String &commandText)
{
   bool done;
   if (Connection)
   {
      done = GetNewStatementHandle() ;

      if (done)
      {
         Connection->Database.LastReturnCode =
            SQLPrepare(hStatement, (SQLWCHAR*)commandText.GetBuffer(), SQL_NTS);
         done = Connection->Database.Success();
         if (done)
         {
            if (Parameters.Count > 0)
               done = BindParameters(hStatement,Parameters);
         }

         // if all is ok then bind columns...
         if (done)
         {
            Connection->Database.LastReturnCode =
               SQLExecute(hStatement); //,(SQLWCHAR*)commandText.GetBuffer(),SQL_NTS);

            // any large data requested?
            if (Connection->Database.LastReturnCode == SQL_NEED_DATA)
               done = SendLargeData();
            else
               done = Connection->Database.Success();

            if (!done)
               Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
         }
      }
   }
   else
      done = FALSE ;

   return(done) ;
}  // end of ExecuteNonQuery

/// <summary>Execute a prepared command</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcCommand::ExecutePreparedCommand()
{
   bool done;
   if (!Connection)
      return(false);
   Connection->Database.LastReturnCode = SQLExecute(hStatement);
   done = Connection->Database.Success() ;
   if (!done)
      Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
   return(done) ;
}  // end of ExecutePreparedCommand

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcCommand::Prepare(TCHAR *commandText)
{
   bool done;
   if (hStatement != SQL_NULL_HSTMT) {
      Connection->Database.LastReturnCode =
         SQLPrepare(hStatement,(SQLWCHAR*)commandText,SQL_NTS) ;
      done = Connection->Database.Success() ;
      if (!done)
         Connection->Database.GetError(SQL_HANDLE_STMT,hStatement) ;
   }
   else
      done = false ;

   return(done) ;
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcCommand::Prepare(TCHAR *commandText,OdbcColumnsCollection &columns)
{
   bool done;
   if (!Connection)
      return(false);

   done = GetNewStatementHandle() ;

   // if all is ok then bind parameters...

   if (done) {
      done = BindColumns(hStatement,columns) ;
      if (done)
         done = Prepare(commandText) ;
   }

   return(done) ;
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
OdbcDataReader *OdbcCommand::OpenReader(
   TCHAR *commandText, OdbcColumnsCollection &columns)
{
   OdbcDataReader *reader = NULL;
   bool done;
   if (!Connection)
      return(reader);

   done = GetNewStatementHandle();

   // if all is ok then bind columns...
   if (done) {
      done = BindColumns(hStatement,columns) ;

      // execute query
      done = Prepare(commandText) ;
      if (done) {
         Connection->Database.LastReturnCode = SQLExecute(hStatement) ;
         done = Connection->Database.Success() ;
         if (!done)
         {
            Connection->Database.GetError(SQL_HANDLE_STMT,hStatement) ;
            // CheckResultFields(flds) ;
         }
         else
            reader = new OdbcDataReader(this);
      }
   }

   return(reader) ;
}  // end of prepare statement

/// <summary>Prepare query</summary>
/// <param name="commandText">command text</param>
/// <param name="params">params to bind too</param>
/// <param name="columns">columns to bind too</param>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
OdbcDataReader *OdbcCommand::OpenReader(
   TCHAR *commandText,
   OdbcParametersCollection &params, OdbcColumnsCollection &columns)
{
   OdbcDataReader *reader = NULL;
   bool done;
   if (!Connection)
      return(reader);

   done = GetNewStatementHandle();

   // if all is ok then bind columns...
   if (done) {

      // bind params
      if (params.Count > 0)
      {
         done = BindParameters(hStatement,params) ;
         if (!done) {
            Connection->Database.GetError(SQL_HANDLE_STMT,hStatement) ;
            return(reader) ;
         }
      }

      // bind columns
      if (columns.Count > 0)
         done = BindColumns(hStatement,columns) ;

      // execute query
      done = Prepare(commandText) ;
      if (done) {
         Connection->Database.LastReturnCode = SQLExecute(hStatement) ;
         done = Connection->Database.Success() ;
         if (!done)
            Connection->Database.GetError(SQL_HANDLE_STMT,hStatement);
         else
            reader = new OdbcDataReader(this);
      }
   }

   return(reader) ;
}  // end of prepare statement

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Define OdbcDataReader class

/// <summary>Move to next available record</summary>
/// <date>Nov/98  (ESob)</date>
/// <returns>true is returned if succeded</returns>
bool OdbcDataReader::Read()
{
   SQLHSTMT stmntHandle = command->GetStatementHandle();
   SQLRETURN rcode = SQLFetch(stmntHandle);
   SetLastReturnCode(rcode);

   rcode = GetLastReturnCode();
   if (rcode != SQL_NO_DATA)
      if (rcode != SQL_SUCCESS)
         GetError(SQL_HANDLE_STMT, command->GetStatementHandle()) ;

   if (Success())
   {
      eof = false;
      bof = false;
   }
   else
   {
      eof = true;
      bof = true;
   }

   return(!eof) ;
}  // end of Next

#pragma endregion
// -----------------------------------------------------------------------------
#pragma region // Declare OdbcFilestream class

#ifdef ODBC_FILESTREAM_SUPPORT_

/// <summary>Passing the transaction is a requirement for Filestreaming (ctor)
/// </summary>
/// <param name="filePath">file path</param>
/// <param name="desiredAccess">desire access (SQL_FILESTREAM_DESIRED_ACCESS)
/// </param>
/// <param name="errorMessages">error message</param>
BOOL OdbcBlobFile::Open(TCHAR *filePath,
   SQL_FILESTREAM_DESIRED_ACCESS desiredAccess,
   Kif::Win32::String *errorMessages)
{
   BOOL done = true;

   m_FileHandle = OpenSqlFilestream(
      filePath,
      desiredAccess,
      0,
      m_TransactionToken,
      m_TransactionTokenSize,
      0);

   if (m_FileHandle == INVALID_HANDLE_VALUE)
   {
      TCHAR szErrMsgDst[]   = TEXT("Error opening file.");
      TCHAR szErr4Writing[] = TEXT(" (for Writing)");
      TCHAR szErr4Reading[] = TEXT(" (for Reading)");
      TCHAR szErr4ReadWrite[] = TEXT(" (for Read/Write)");
      TCHAR szErr4Unkown[]  = TEXT(" (for Unknown Access)");

      if (errorMessages)
      {
         errorMessages->Concat(szErrMsgDst,SizeOf(szErrMsgDst));
         if (desiredAccess == DESIRED_ACCESS_READ)
            errorMessages->Concat(szErr4Reading,SizeOf(szErr4Reading));
         else
         if (desiredAccess == DESIRED_ACCESS_WRITE)
            errorMessages->Concat(szErr4Writing,SizeOf(szErr4Writing));
         else
         if (desiredAccess == DESIRED_ACCESS_READ)
            errorMessages->Concat(szErr4ReadWrite,SizeOf(szErr4ReadWrite));
         else
            errorMessages->Concat(szErr4Unkown,SizeOf(szErr4Unkown));
      }
      else
         throw szErrMsgDst;

      done = false;
   }
   return done;
}  // end of Open

/// <summary>Copy given file to a Blob File</summary>
/// <param name="sourceFilePath">source file path</param>
/// <param name="destinationFilePath">destination file path</param>
/// <param name="errorMessages">error message (if any) will be appended
/// if an instance of String is given, else the caller will be signal that
/// something went wrong by returning -1</param>
/// <returns>the total number of bytes written is returned, -1 if an error
/// was found</returns>
DWORD OdbcBlobFile::FromFileToBlob(
   TCHAR *sourceFilePath, TCHAR *destinationFilePath,
   Kif::Win32::String *errorMessages)
{
   DWORD totBytesWritten = 0;
   BOOL bRetCode = FALSE;

   HANDLE srcHandle = INVALID_HANDLE_VALUE;
   HANDLE dstHandle = INVALID_HANDLE_VALUE;
   BYTE   buffer[COPYBUFFERSIZE] = { 0 };

   TCHAR szErrMsgSrc[]  = TEXT("Error opening source file.");
   TCHAR szErrMsgRead[] = TEXT("Error reading source file.");

   try
   {
      if ( (srcHandle = CreateFile(
         sourceFilePath,
         GENERIC_READ,
         FILE_SHARE_READ,
         NULL,
         OPEN_EXISTING,
         FILE_FLAG_SEQUENTIAL_SCAN,
         NULL)) == INVALID_HANDLE_VALUE )
      {
         if (errorMessages)
            errorMessages->Copy(szErrMsgSrc,SizeOf(szErrMsgSrc));
         return false;
      }

      if (OpenWrite(destinationFilePath, errorMessages))
         return false;

      DWORD bytesRead = 0;
      bRetCode = TRUE;

      do
      {
         if ( ReadFile(srcHandle,buffer,COPYBUFFERSIZE,&bytesRead,NULL) == 0)
         {
            if (errorMessages)
               errorMessages->Copy(szErrMsgRead,SizeOf(szErrMsgRead));
            bRetCode = FALSE;
            break;
         }

         if (bytesRead > 0)
         {
        		if (Write(buffer, bytesRead, errorMessages) == 0)
               break;
            else
               totBytesWritten += bytesRead;
         }
      }  while (bytesRead > 0);
	}
   catch( TCHAR *szErrMsg )
   {
      if (errorMessages)
         errorMessages->Copy(szErrMsg);
      bRetCode = FALSE;
   }

   if (bRetCode)
      totBytesWritten = -1;

   if ( srcHandle != INVALID_HANDLE_VALUE )
      CloseHandle(srcHandle);

   if ( dstHandle != INVALID_HANDLE_VALUE )
      CloseHandle(dstHandle);

   return totBytesWritten;
}  // end of FromFileToBlob

/// <summary>Given a file path, store given buffer in to it.</summary>
/// <param name="filePath">destination file path</param>
/// <param name="buffer">buffer</param>
/// <param name="bufferLength">buffer length</param>
/// <param name="errorMessages">error message (if any) will be appended
/// if an instance of String is given, else the caller will be signal that
/// something went wrong by returning -1</param>
/// <returns>the total number of bytes written is returned, -1 if an error
/// was found</returns>
DWORD OdbcBlobFile::ToFile(TCHAR *filePath, BYTE *buffer, size_t bufferLength,
   Kif::Win32::String *errorMessages)
{
   return 0;
}  // end of FromBufferToFile

/// <summary>Copy given Blob-file to a File</summary>
/// <param name="sourceFilePath">source file path</param>
/// <param name="destinationFilePath">destination file path</param>
/// <param name="errorMessages">error message (if any) will be appended
/// if an instance of String is given, else the caller will be signal that
/// something went wrong by returning -1</param>
/// <returns>the total number of bytes written is returned, -1 if an error
/// was found</returns>
DWORD OdbcBlobFile::FromBlobToFile(
   TCHAR *sourceFilePath, TCHAR *destinationFilePath,
   Kif::Win32::String *errorMessages)
{
   DWORD totBytesWritten = 0;
   BOOL bRetCode = FALSE;

   HANDLE flHandle = INVALID_HANDLE_VALUE;
   BYTE   buffer[COPYBUFFERSIZE] = { 0 };

   TCHAR szErrMsgSrc[]  = TEXT("Error creating/opening destination file.");
   TCHAR szErrMsgWrite[] = TEXT("Error writing to destination file.");

   try
   {
      if ( (flHandle = CreateFile(
         destinationFilePath,
         GENERIC_WRITE,
         FILE_SHARE_WRITE,
         NULL,
         CREATE_ALWAYS,
         0,
         NULL)) == INVALID_HANDLE_VALUE )
      {
         if (errorMessages)
            errorMessages->Copy(szErrMsgSrc,SizeOf(szErrMsgSrc));
         return false;
      }

      if (OpenRead(sourceFilePath, errorMessages))
         return false;

      DWORD bytesWriten = 0;
      DWORD bytesRead = 0;
      bRetCode = TRUE;

      do
      {
         bytesRead = Write(buffer, COPYBUFFERSIZE, errorMessages);

         if (bytesRead > 0)
         {
            if ( WriteFile(flHandle,buffer,bytesRead,&bytesWriten,NULL) == 0)
            {
               if (errorMessages)
                  errorMessages->Copy(szErrMsgWrite,SizeOf(szErrMsgWrite));
               bRetCode = FALSE;
               break;
            }
            else
               totBytesWritten += bytesRead;
         }
      }  while (bytesRead > 0);
	}
   catch( TCHAR *szErrMsg )
   {
      if (errorMessages)
         errorMessages->Copy(szErrMsg);
      bRetCode = FALSE;
   }

   if (bRetCode)
      totBytesWritten = -1;

   if ( flHandle != INVALID_HANDLE_VALUE )
      CloseHandle(flHandle);

   if ( m_FileHandle != INVALID_HANDLE_VALUE )
   {
      CloseHandle(m_FileHandle);
      m_FileHandle = INVALID_HANDLE_VALUE;
   }

   return totBytesWritten;
}  // end of FromBlobToFile

#endif

#pragma endregion
// -----------------------------------------------------------------------------

}  // end of Kif::Win32::OdbcClient namespace
}  // end of Kif::Win32::Data namespace
}  // end of Kif::Win32 namespace
}  // end of Kif namespace
